#ifndef __POINT_2_HPP__
#define __POINT_2_HPP__

#define USE_NLOHMANN_JSON 1
#define USE_FMT_TO_STRING 1

#include <stdint.h>
#include <assert.h>
#if USE_NLOHMANN_JSON
#include "nlohmann/json.hpp"
#endif

#if USE_FMT_TO_STRING
#include "spdlog/fmt/fmt.h"
#endif

namespace PocketSurvival::Engine
{
    struct Point2
    {
        union
        {
            int64_t value;
            struct
            {
                int32_t posX;
                int32_t posY;
            };
        };

        constexpr Point2() noexcept;
        constexpr Point2(int32_t x, int32_t y) noexcept;
    
        inline int64_t toKey() const;

        constexpr inline Point2 &operator+=(const Point2 &left);
        constexpr inline Point2 &operator-=(const Point2 &left);
        constexpr inline Point2 &operator*=(int32_t c);
        constexpr inline Point2 &operator/=(int32_t c);

        friend constexpr inline bool operator==(const Point2 &left, const Point2 &right)
        {
            return (left.posX == right.posX) && (left.posY == right.posY);
        }
        friend constexpr inline bool operator!=(const Point2 &left, const Point2 &right)
        {
            return !(left == right);
        }
        friend constexpr inline Point2 operator+(const Point2 &left, const Point2 &right)
        {
            return Point2(left.posX + right.posX, left.posY + right.posY);
        }
        friend constexpr inline Point2 operator-(const Point2 &left, const Point2 &right)
        {
            return Point2(left.posX - right.posX, left.posY - right.posY);
        }
        friend constexpr inline Point2 operator*(const Point2 &left, int32_t c)
        {
            return Point2(left.posX * c, left.posY * c);
        }
        friend constexpr inline Point2 operator*(int32_t c, const Point2 &right)
        {
            return Point2(right.posX * c, right.posY * c);
        }
        friend constexpr inline Point2 operator+(const Point2 &left)
        {
            return left;
        }
        friend constexpr inline Point2 operator-(const Point2 &left)
        {
            return Point2(-left.posX, -left.posY);
        }
        friend constexpr inline Point2 operator/(const Point2 &left, int32_t divisor)
        {
            assert(divisor < 0 || divisor > 0);
            return Point2(left.posX / divisor, left.posY / divisor);
        }
        inline bool operator<(const Point2& other) const
        {
            if(toKey() < other.toKey())
            {
                return true;
            }
            return false;
        }

#if USE_NLOHMANN_JSON
        friend inline void to_json(nlohmann::json &json, const Point2 &point);
        friend inline void from_json(const nlohmann::json &json, Point2 &point);
#endif

#if USE_FMT_TO_STRING
        inline std::string toString() const
        {
            return fmt::format("{},{}",posX,posY);
        }
#endif

        inline size_t operator()(const Point2& point) const
        {
            return point.toKey();
        }
        inline bool operator()(const Point2& left, const Point2 &right) const noexcept
        {
            return (left == right);
        }
    };

    constexpr inline Point2::Point2() noexcept : posX(0), posY(0) {}

    constexpr inline Point2::Point2(int32_t x, int32_t y) noexcept : posX(x), posY(y) {}

    inline int64_t Point2::toKey() const
    {
        return value;
    }

    constexpr inline Point2 &Point2::operator+=(const Point2 &right)
    {
        posX += right.posX;
        posY += right.posY;
        return *this;
    }

    constexpr inline Point2 &Point2::operator-=(const Point2 &right)
    {
        posX -= right.posX;
        posY -= right.posY;
        return *this;
    }

    constexpr inline Point2 &Point2::operator*=(int32_t c)
    {
        posX *= c;
        posY *= c;
        return *this;
    }

    constexpr inline Point2 &Point2::operator/=(int32_t divisor)
    {
        assert(divisor > 0 || divisor < 0);
        posX /= divisor;
        posY /= divisor;
        return *this;
    }

#if USE_NLOHMANN_JSON

    inline void to_json(nlohmann::json &json, const Point2 &point)
    {
        json = {{"X",point.posX},{"Y",point.posY}};
    }
    inline void from_json(const nlohmann::json &json, Point2 &point)
    {
        point.posX = json["X"].get<int32_t>();
        point.posY = json["Y"].get<int32_t>();
    }

#endif

}



#undef SE_NLOHMANN_JSON
#undef SE_FMT_TO_STRING

#endif
